﻿using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using Mondial2014.Models.Context;
using Mondial2014.Models.Monitoring;
using Mondial2014.Utils.Events;

namespace Mondial2014.Utils
{
    public static class KnockoutGamblerScoreCalculator
    {
        private static readonly object Sync = new object();

        public static int CalculateScoreForPlayerUntilDate(int gamblerId, DateTime untilDate)
        {
            using (var context = new Mondial2014DbContext())
            {
                var knockoutUpdates = context.Updates
                    .OfType<KnockoutUpdate>()
                    .Include(u => u.Team).Include(u => u.Stage)
                    .Where(u => u.Date <= untilDate)
                    .ToArray()
                    .OrderByDescending(u => u.Date)
                    .GroupBy(u => u.Team.Id.ToString() + ":" + u.Stage.Id.ToString())
                    .Select(g => g.First());

                var score = knockoutUpdates
                    .Where(update =>
                        context.KnockoutGambles.Include(g => g.Gambler).Include(g => g.Team).Include(g => g.Stage)
                    .Any(g =>
                            g.Gambler.Id == gamblerId &&
                            g.Team.Id == update.Team.Id &&
                            g.Stage.Id == update.Stage.Id))
                    .Sum(update => update.Stage.Level + 1);
                
                return score;
            }
        }

        [EventDispatcher.Subscribe]
        public static void CalculateScores(KnockoutsStageUpdatedEvent knockoutsStageUpdated)
        {
            lock (Sync)
            {
                using (var context = new Mondial2014DbContext())
                {
                    foreach (var gambler in context.Gamblers.ToArray())
                    {
                        var gambler1 = gambler;
                        
                        var score = 0;

                        for (var level = 1; level < 6; level++)
                        {
                            var teams = GetTeamIdsForStage(context, level);
                            var level1 = level;
                            var gambles =
                                context.KnockoutGambles.Where(g => g.Gambler.Id == gambler1.Id && g.Stage.Level == level1);
                            score += Enumerable.Sum(gambles.Where(gamble => teams.Contains(gamble.Team.Id)), gamble => (level + 1));
                        }

                        gambler1.KnockoutStageScore = score;
                    }

                    context.SaveChanges();

                    EventDispatcher.PostEvent(new ScoresUpdatedEvent());
                }
            }
        }

        private static IEnumerable<int> GetTeamIdsForStage(Mondial2014DbContext context, int level)
        {
            if (level == 1)
            {
                return context.Teams.Where(t => t.Last16).Select(t => t.Id);
            }
            if (level == 2)
            {
                return context.Teams.Where(t => t.Quarter).Select(t => t.Id);
            }
            if (level == 3)
            {
                return context.Teams.Where(t => t.Semi).Select(t => t.Id);
            }
            if (level == 4)
            {
                return context.Teams.Where(t => t.Final).Select(t => t.Id);
            }
            if (level == 5)
            {
                return context.Teams.Where(t => t.Winner).Select(t => t.Id);
            }
            throw new Exception("level is " + level);
        }
    }
}